package fr.mystnihon.game.framework.impl.view;

import java.text.DecimalFormat;

import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;

public abstract class RenderThread<T> extends Thread {
	protected static final String TAG = MainThread.class.getSimpleName();

	// desired fps
	protected final static int MAX_FPS = 50;
	// maximum number of frames to be skipped
	protected final static int MAX_FRAME_SKIPS = 5;
	// the frame period
	protected final static int FRAME_PERIOD = 1000 / MAX_FPS;

	/* Stuff for stats */
	protected DecimalFormat df = new DecimalFormat("0.##"); // 2 dp
	// we'll be reading the stats every second
	protected final static int STAT_INTERVAL = 1000; // ms
	// the average will be calculated by storing
	// the last n FPSs
	protected final static int FPS_HISTORY_NR = 10;
	// the status time counter
	protected long statusIntervalTimer = 0l;
	// number of frames skipped since the game started
	protected long totalFramesSkipped = 0l;
	// number of frames skipped in a store cycle (1 sec)
	protected long framesSkippedPerStatCycle = 0l;

	// number of rendered frames in an interval
	protected int frameCountPerStatCycle = 0;
	protected long totalFrameCount = 0l;
	// the last FPS values
	protected double fpsStore[];
	// the number of times the stat has been read
	protected long statsCount = 0;
	// the average FPS since the game started
	protected double averageFps = 0.0;

	// Surface holder that can access the physical surface
	protected SurfaceHolder surfaceHolder;
	// The actual view that handles inputs
	// and draws to the surface
	protected T gamePanel;
	// flag to hold game state
	protected boolean running;

	// flag to update game for pause
	protected boolean paused = false;

	public RenderThread(SurfaceHolder surfaceHolder, T gamePanel) {
		super();
		this.surfaceHolder = surfaceHolder;
		this.gamePanel = gamePanel;
	}

	public void setRunning(boolean running) {
		this.running = running;
	}

	public void pause() {
		setPause(true);
	}

	public void unpause() {
		setPause(false);
	}

	private void setPause(boolean pause) {
		paused = pause;
	}

	@Override
	public void run() {
		Canvas canvas;
		Log.d(TAG, "Starting game loop");
		// initialise timing elements for stat gathering
		initTimingElements();
		long beginTime = System.currentTimeMillis();
		long currTime = beginTime;
		long timeDiff; // the time it took for the cycle to execute
		int sleepTime; // ms to sleep (<0 if we're behind)
		int framesSkipped; // number of frames being skipped

		sleepTime = 0;

		while (running) {
			canvas = null;
			// try locking the canvas for exclusive pixel editing
			// in the surface
			try {
				canvas = this.surfaceHolder.lockCanvas();
				synchronized (surfaceHolder) {
					beginTime = System.currentTimeMillis();
					long elapsedTime = System.currentTimeMillis() - currTime;
					currTime += elapsedTime;

					framesSkipped = 0; // resetting the frames skipped
					if (!paused) {
						// update game state
						update(elapsedTime);
					}
					// render state to the screen
					// draws the canvas on the panel
					render(canvas);
					// calculate how long did the cycle take
					timeDiff = System.currentTimeMillis() - beginTime;
					// calculate sleep time
					sleepTime = (int) (FRAME_PERIOD - timeDiff);

					if (sleepTime > 0) {
						// if sleepTime > 0 we're OK
						try {
							// send the thread to sleep for a short period
							// very useful for battery saving
							Thread.sleep(sleepTime);
						} catch (InterruptedException e) {
						}
					}

					while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
						if (!paused) {
							// we need to catch up
							update(elapsedTime); // update
													// without
													// rendering
						}
						sleepTime += FRAME_PERIOD; // add frame period to check
													// if in next frame
						framesSkipped++;
					}

					// for statistics
					framesSkippedPerStatCycle += framesSkipped;
				}
			} finally {
				// in case of an exception the surface is not left in
				// an inconsistent state
				if (canvas != null) {
					surfaceHolder.unlockCanvasAndPost(canvas);
				}
			} // end finally
		}
	}

	private void initTimingElements() {
		// initialise timing elements
		fpsStore = new double[FPS_HISTORY_NR];
		for (int i = 0; i < FPS_HISTORY_NR; i++) {
			fpsStore[i] = 0.0;
		}
		Log.d(TAG + ".initTimingElements()",
				"Timing elements for stats initialised");
	}

	public abstract void update(long elepsedTime);

	public abstract void render(Canvas canvas);
}
